package org.dromara.common.encrypt.core;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.annotation.EncryptField;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


/**
 * @Author 刘武贵
 * @Date 2024/8/15 22:00
 * @Description 功能描述：加密管理类
 */
@Slf4j
@NoArgsConstructor
public class EncryptorManager {

    /**
     * 缓存加密器
     * 创建一个并发安全的映射，用于存储加密上下文和对应的加密器
     * key 密上下文 用于encryptor传递必要的参数 value 加解者
     */
    Map<EncryptContext, IEncryptor> encryptorMap = new ConcurrentHashMap<>();

    /**
     * 类加密字段缓存
     * 创建一个用于缓存字段的线程安全的映射
     * 该映射的键是类（Class<?>），值是该类中被关注的字段的集合（Set<Field>）
     * 使用ConcurrentHashMap保证在多线程环境下安全地访问和更新
     */
    Map<Class<?>, Set<Field>> fieldCache = new ConcurrentHashMap<>();

    /**
     * 构造方法传入类加密字段缓存
     * 构造函数，初始化EncryptorManager
     * 该构造函数主要负责根据给定的包名扫描和加载加密器别名
     *
     * @param typeAliasesPackage 实体类包.包名，用于指定要扫描的包，以发现所有的加密器别名
     */
    public EncryptorManager(String typeAliasesPackage) {
        // 扫描指定包下的所有类，寻找带有特定注解的加密器类
        scanEncryptClasses(typeAliasesPackage);
    }


    /**
     * 获取类加密字段缓存
     * 根据给定的源类获取字段缓存
     *
     * @param sourceClazz 源类的Class对象，用于检索字段缓存
     * @return 如果fieldCache不为空且包含给定源类的字段缓存，则返回该字段缓存集合；否则返回null
     *
     * 此方法的目的是提供一种快速获取字段缓存的方法，避免重复计算或查找，以提高性能
     * 它依赖于fieldCache内部缓存机制，如果缓存中不存在请求的字段，将返回null，表示需要进行进一步的处理或查找
     */
    public Set<Field> getFieldCache(Class<?> sourceClazz) {
        if (ObjectUtil.isNotNull(fieldCache)) {
            return fieldCache.get(sourceClazz);
        }
        return null;
    }

    /**
     * 注册加密执行者到缓存
     * 根据加密上下文注册并获取加密器
     * 如果加密器已经存在，则直接从缓存中返回；否则，根据加密算法的类信息创建新的加密器实例，并将其缓存
     *
     * @param encryptContext 加密执行者需要的相关配置参数。加密上下文，包含加密所需的信息
     * @return IEncryptor 加密器实例
     */
    public IEncryptor registAndGetEncryptor(EncryptContext encryptContext) {
        // 检查是否已经存在该加密器，提高性能，避免重复创建
        if (encryptorMap.containsKey(encryptContext)) {
            return encryptorMap.get(encryptContext);
        }
        // 根据加密算法的类信息和加密上下文创建新的加密器实例
        IEncryptor encryptor = ReflectUtil.newInstance(encryptContext.getAlgorithm().getClazz(), encryptContext);
        // 将新创建的加密器实例缓存起来，以便下次快速获取
        encryptorMap.put(encryptContext, encryptor);
        return encryptor;
    }

    /**
     * 移除缓存中的加密执行者
     * 本方法负责从加密器映射中移除指定的加密上下文对象这是因为当不再需要某个加密器时，
     * 从映射中移除可以减少内存占用并避免潜在的内存泄漏
     *
     * @param encryptContext 加密执行者需要的相关配置参数。要移除的加密上下文对象，它唯一标识了一个加密器
     */
    public void removeEncryptor(EncryptContext encryptContext) {
        this.encryptorMap.remove(encryptContext);
    }

    /**
     * 根据配置进行加密。会进行本地缓存对应的算法和对应的秘钥信息。
     * 根据提供的加密上下文，使用指定的加密器对值进行加密
     * 此方法首先根据加密上下文注册或获取加密器，然后使用该加密器对提供的值进行加密
     *
     * @param value          待加密的值
     * @param encryptContext 加密相关的配置信息，包含加密所需的信息，如加密算法等
     * @return 加密后的字符串
     */
    public String encrypt(String value, EncryptContext encryptContext) {
        // 根据加密上下文注册或获取加密器
        IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
        // 使用加密器对值进行加密，并指定编码方式
        return encryptor.encrypt(value, encryptContext.getEncode());
    }

    /**
     * 根据配置进行解密
     * 根据提供的加密上下文解密字符串值
     *
     * @param value          待解密的值
     * @param encryptContext 加密相关的配置信息，包含解密所需的上下文信息
     */
    public String decrypt(String value, EncryptContext encryptContext) {
        // 根据加密上下文注册并获取加密器
        IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
        // 使用加密器解密字符串值
        return encryptor.decrypt(value);
    }

    /**
     * 通过 typeAliasesPackage 设置的扫描包 扫描缓存实体
     * 扫描并缓存加密类信息
     * 本方法旨在从给定的包中扫描所有类，并缓存其中包含加密字段的类信息
     * 主要用于加速运行时对类中加密字段的访问，提高性能
     *
     * @param typeAliasesPackage 类别别名包名，用于确定扫描的范围
     */
    private void scanEncryptClasses(String typeAliasesPackage) {
        // 创建资源解析器，用于根据包名扫描类路径下的资源
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        // 创建元数据读取工厂，用于读取类的元数据
        CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
        // 将包名分割成数组，以处理多个包的情况
        String[] packagePatternArray = StringUtils.splitPreserveAllTokens(typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        // 定义类路径的前缀，用于资源解析器扫描类文件
        String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
        try {
            // 遍历每个包模式，进行扫描
            for (String packagePattern : packagePatternArray) {
                // 将类名转换为资源路径
                String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
                // 获取类路径下所有类文件资源
                Resource[] resources = resolver.getResources(classpath + path + "/*.class");
                // 遍历每个类文件资源
                for (Resource resource : resources) {
                    // 读取类的元数据
                    ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
                    // 根据类的全限定名获取类对象
                    Class<?> clazz = Resources.classForName(classMetadata.getClassName());
                    // 获取类中所有被标记为加密的字段
                    Set<Field> encryptFieldSet = getEncryptFieldSetFromClazz(clazz);
                    // 如果类中含有加密字段，则将其缓存起来
                    if (CollUtil.isNotEmpty(encryptFieldSet)) {
                        fieldCache.put(clazz, encryptFieldSet);
                    }
                }
            }
        } catch (Exception e) {
            log.error("初始化数据安全缓存时出错:{}", e.getMessage());
        }
    }

    /**
     * 获得一个类的加密字段集合
     * 从类中获取所有使用了EncryptField注解的字段集合
     *
     * @param clazz 需要检查的类
     * @return 包含所有使用了EncryptField注解的字段的集合
     */
    private Set<Field> getEncryptFieldSetFromClazz(Class<?> clazz) {
        // 初始化字段集合
        Set<Field> fieldSet = new HashSet<>();

        // 判断clazz如果是接口,内部类,匿名类就直接返回
        if (clazz.isInterface() || clazz.isMemberClass() || clazz.isAnonymousClass()) {
            return fieldSet;
        }
        // 从当前类及其所有超类中收集字段
        while (clazz != null) {
            // 获取类中声明的所有字段
            Field[] fields = clazz.getDeclaredFields();
            // 将这些字段添加到集合中
            fieldSet.addAll(Arrays.asList(fields));
            // 继续检查超类
            clazz = clazz.getSuperclass();
        }

        // 筛选出标记为EncryptField且类型为String的字段
        fieldSet = fieldSet.stream().filter(field ->
                field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class)
            .collect(Collectors.toSet());

        // 设置字段可访问，以便后续操作
        for (Field field : fieldSet) {
            field.setAccessible(true);
        }

        // 返回筛选后的字段集合
        return fieldSet;
    }

}
